package lapsnake.model.data;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;

import owg.util.Calc;

/**A basic property. Objects may listen to changes to the property, or change its value, notifying other listeners.*/
public class VetoableProperty<T> extends Property<T> 
{
	/**Veto listeners*/
	private ArrayList<WeakReference<VetoableChangeListener>> vetoListeners = new ArrayList<>();
	
	/**Create a new property with the given initial value. <b>It is illegal to pass a null value to this constructor. 
	 * Also, the value type must be an exact match for the generic type.</b>*/
	public VetoableProperty(T initialValue)
	{
		super(initialValue);
	}
	/**Create a new property with the given initial value. <b>It is illegal to pass a null value to this constructor. 
	 * Also, the value type must be an exact match for the generic type.</b><br><br> 
	 * An initial veto listener can be added. This listener will be notified before all other listeners.*/
	public VetoableProperty(VetoableChangeListener listener, T initialValue)
	{
		super(initialValue);
		addVetoListener(listener);
	}
	/**Create a new property with the given initial value. <b>It is illegal to pass a null value to this constructor. 
	 * Also, the value type must be an exact match for the generic type.</b> <br><br>
	 * Optionally, a listener may be added immediately.*/
	public VetoableProperty(TypedListener<? super T> listener, T initialValue)
	{
		super(listener, initialValue);
	}
	/**Create a new property with the given type and no initial value.*/
	public VetoableProperty(Class<T> type) {
		super(type);
	}
	/**Create a new property with the given type and initial value.*/
	public VetoableProperty(Class<T> type, T initialValue) {
		super(type, initialValue);
	}
	/**Create a new property with the given type and initial value.<br><br> 
	 * Optionally, a listener may be added immediately.*/
	public VetoableProperty(TypedListener<? super T> listener, Class<T> type, T initialValue) {
		super(listener, type, initialValue);
	}
	/**Create a new property with the given type.<br><br> 
	 * An initial veto listener can be added. This listener will be notified before all other listeners.*/
	public VetoableProperty(VetoableChangeListener listener, Class<T> type) {
		super(type);
		addVetoListener(listener);
	}
	
	/**
	 * Set the current value without firing any events. If the type is not registered as an immutable type ({@link #registerImmutableClass(Class)}) 
	 * then a copy (via Kryo) will be used internally. This method will never generated Veto Exceptions because it does not fire any events.
	 * It should only be used for initialization. It is illegal to call this if there are veto listeners attached to the property!
	 * @param value The new value for the property. May be null if this makes sense in the application.
	 */
	public void setInternal(T value)
	{
		try
		{
			change(value, null, false);
		}
		catch (PropertyVetoException e)
		{
			System.err.println("This never happens.");
			e.printStackTrace();
		}
	}
	/**
	 * Set the current value. If the type is not registered as an immutable type ({@link #registerImmutableClass(Class)}) 
	 * then a copy (via Kryo) will be used internally. 
	 * @param value The new value for the property. May be null if this makes sense in the application.
	 * @param exclude An Listener which will <b>not</b> receive an action event when this is called. May be null. Is not relevant if fireEvents is false.
	 * This is useful to avoid infinite recursion if the value is changed from an action event handler. 
	 * Note that this will <b>not</b> exclude the object from receiving a vetoable change event if it is registered as a veto listener.
	 * @param fireEvents Whether to fire any action events at all.
	 * @throws PropertyVetoException if there was a veto listener which prevented the change.
	 */
	public void change(T value, Object exclude, boolean fireEvents) throws PropertyVetoException
	{
		Iterator<WeakReference<VetoableChangeListener>> iterator = vetoListeners.iterator();
		while (iterator.hasNext())
		{
			WeakReference<VetoableChangeListener> v = iterator.next();
			VetoableChangeListener l = v.get();
			if(l == null)
				iterator.remove();
			else
				l.vetoableChange(new PropertyChangeEvent(this, "value", get(), value));
		}
		super.set(value, exclude, fireEvents);
	}
	
	/**
	 * Set the current value. If the type is not registered as an immutable type ({@link #registerImmutableClass(Class)}) 
	 * then a copy (via Kryo) will be used internally. For vetoable properties, please use {@link #change(Object, Object, boolean)} instead.
	 * @param value The new value for the property. May be null if this makes sense in the application.
	 * @param exclude An Listener which will <b>not</b> receive an action event when this is called. May be null. Is not relevant if fireEvents is false.
	 * This is useful to avoid infinite recursion if the value is changed from an action event handler.
	 * @param fireEvents Whether to fire any action events at all.
	 * @throws IllegalArgumentException if there was a veto listener which prevented the change.
	 */
	@Deprecated
	public void set(T value, Object exclude, boolean fireEvents) throws IllegalArgumentException
	{
		try
		{
			change(value, exclude, fireEvents);
		}
		catch (PropertyVetoException e)
		{
			throw new IllegalArgumentException(e);
		}
		
	}
	/**Receive events that allow throwing a veto exception when the value is going to change. 
	 * It is illegal to add a listener twice using this method. It is illegal to add null as a listener.
	 * This will not prevent the listener from being garbage collected. It will be automatically removed if this happens.*/
	public void addVetoListener(VetoableChangeListener l)
	{
		if(l == null)
		{
			System.err.println("Cannot add null as action listener to "+this);
			return;
		}
		int oldIndex = Calc.indexOfWeakReference(vetoListeners, l);
		if(oldIndex != -1)
		{
			System.err.println("Warning: Tried to add "+l+" as veto listener to "+this+" but it was already added at "+oldIndex);
			return;
		}
		vetoListeners.add(new WeakReference<VetoableChangeListener>(l));
	}
	
	/**Remove the indicated listener. Returns false if it has not been added yet. 
	 * Note that listeners will be removed automatically if they are garbage collected.*/
	public boolean removeVetoListener(VetoableChangeListener l)
	{
		int index = Calc.indexOfWeakReference(vetoListeners, l);
		if(index == -1)
			return false;
		vetoListeners.remove(index);
		return true;
	}
}
